﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.IO;
using Gert3D;

namespace RayTracer
{
  public partial class Form1 : Form
  {
    private List<Mesh> _meshes = new List<Mesh>();
    private List<PointLight> _lights = new List<PointLight>();
    private Bitmap _image = null;
    private Camera _camera = new Camera();
    private Mesh mesh = new Mesh();
    private IRenderer renderer = null;

    [DllImport("Kernel32.dll")]
    private static extern bool QueryPerformanceCounter(out long lpPerformanceCount);

    [DllImport("Kernel32.dll")]
    private static extern bool QueryPerformanceFrequency(out long lpFrequency);

    public Form1()
    {
      InitializeComponent();

      long tick1, tick2, freq;
      QueryPerformanceFrequency(out freq);

      QueryPerformanceCounter(out tick1);
      mesh.FromObjFile("figur.obj");
      QueryPerformanceCounter(out tick2);
      double time = (tick2 - tick1) / (double)freq;
      LogTiming("Load Mesh", time);

      //Shadow Triangles
      //mesh.Faces.Add(new Triangle(new Vertex(1, -1, 10), new Vertex(0, 1, 10), new Vertex(-1, -1, 10)));
      //mesh.Faces.Add(new Triangle(new Vertex(1, 0, 9), new Vertex(0, 2, 9), new Vertex(-1, 0, 9)));

      //Plane
      //mesh.Faces.Add(new Triangle(new Vertex(1, 1, 10), new Vertex(-1, 1, 10), new Vertex(-1, -1, 10)));
      //mesh.Faces.Add(new Triangle(new Vertex(-1, -1, 10), new Vertex(1, -1, 10), new Vertex(1, 1, 10)));

      _meshes.Add(mesh);

      int faces = 0;
      foreach (Mesh m in _meshes)
      {
        faces += m.Faces.Count;
      }
      LogTiming("Faces", faces);

      QueryPerformanceCounter(out tick1);
      Matrix transform = Matrix.Translate(0, 0, 5);
      mesh.Transform(transform);
      QueryPerformanceCounter(out tick2);
      time = (tick2 - tick1) / (double)freq;
      LogTiming("Transform Mesh", time);

      

      _lights.Add(new PointLight(new Vector3(-10, 5, 0), 1, new Color4(1, 0.85, 0.8)));
      _lights.Add(new PointLight(new Vector3(10, 0, 0), 1, new Color4(0.85, 0.85, 1)));
      //_lights.Add(new PointLight(new Vector3(-5, 0, 3), 1, new Color4(1, 0.95, 0.9)));

      renderer = new RaytracerVoxeled();

      QueryPerformanceCounter(out tick1);
      if (!renderer.Initialize(_meshes, _lights))
        throw new InvalidOperationException("Could not initialize Raytracer!");
      QueryPerformanceCounter(out tick2);
      time = (tick2 - tick1) / (double)freq;
      LogTiming("Init Renderer", time);

      LogSplitter();
    }

    private void LogLine(string line)
    {
      tbLog.Text += line + Environment.NewLine;
    }

    private void LogTiming(string caption, double time)
    {
      tbLog.Text += caption + ": " + time.ToString("0.#########") + Environment.NewLine;
    }

    private void LogSplitter()
    {
      LogLine(new string('-', 20));
    }

    private void btnRender_Click(object sender, EventArgs e)
    {
      _camera.FieldOfView = 45;
      _camera.Width = pbImage.Width;
      _camera.Height = pbImage.Height;
      _camera.Transform = Matrix.Translate(0, 0, 0);
      Ray[,] rays = _camera.CalculateRays();

      Color4[,] result = new Color4[pbImage.Width, pbImage.Height];

      _image = new Bitmap(pbImage.Width, pbImage.Height);

      int bucketSize = 32;

      int width = (int)(pbImage.Width / (double)bucketSize) + 1;
      int heigth = (int)(pbImage.Height / (double)bucketSize) + 1;

      long freq, tick1, tick2, allRender, allTransfer, allShow, total1, total2;
      allRender = 0;
      allShow = 0;
      allTransfer = 0;
      QueryPerformanceFrequency(out freq);

      QueryPerformanceCounter(out total1);
      Rectangle2 region;
      for (int y = 0; y < heigth; y++)
      {
        region.Top = y * bucketSize;
        region.Bottom = Math.Min(_image.Height - 1, ((y + 1) * bucketSize) - 1);
        for (int x = 0; x < width; x++)
        {
          region.Left = x * bucketSize;
          region.Right = Math.Min(_image.Width - 1, ((x + 1) * bucketSize) - 1);
          QueryPerformanceCounter(out tick1);
          renderer.RenderRegion(region, rays, result);
          QueryPerformanceCounter(out tick2);
          allRender += tick2 - tick1;

          QueryPerformanceCounter(out tick1);
          for (int py = (int)region.Top; py <= (int)region.Bottom; py++)
          {
            for (int px = (int)region.Left; px <= (int)region.Right; px++)
            {
              int red = (int)Math.Max(0, Math.Min(255, result[px, py].R * 255));
              int green = (int)Math.Max(0, Math.Min(255, result[px, py].G * 255));
              int blue = (int)Math.Max(0, Math.Min(255, result[px, py].B * 255));

              _image.SetPixel(px, py, Color.FromArgb(red, green, blue));
            }
          }
          QueryPerformanceCounter(out tick2);
          allTransfer += tick2 - tick1;

          QueryPerformanceCounter(out tick1);
          pbImage.Image = _image;
          
          Application.DoEvents();
          if (!this.Created)
            return;

          QueryPerformanceCounter(out tick2);
          allShow += tick2 - tick1;
        }
      }
      pbImage.Image = _image;
      QueryPerformanceCounter(out total2);

      double time = allRender / (double)freq;
      LogTiming("Render", time);

      time /= rays.Length;
      LogTiming("Per Pixel", time);

      time = allTransfer / (double)freq;
      LogTiming("Transfer", time);

      time = allShow / (double)freq;
      LogTiming("Show", time);

      time = (total2 - total1) / (double)freq;
      LogTiming("TOTAL", time);

      LogSplitter();
    }

    private void btnSave_Click(object sender, EventArgs e)
    {
      if (saveDialog.ShowDialog() == DialogResult.OK)
      {
        string file = saveDialog.FileName;
        if (Path.GetExtension(file) == string.Empty)
          file += ".png";

        _image.Save(file);
      }
    }

    private void btnClipboard_Click(object sender, EventArgs e)
    {
      Clipboard.SetImage(_image);
    }

  }
}
